<?php

namespace App\Jobs;

use App\Models\DDBook;
use App\Models\JDBook;
use App\Models\Spider;
use ForceUTF8\Encoding;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;

class CrawlKeys implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $spider;
    private $proc;
    private $pipes;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($id)
    {
        //
        $this->spider = Spider::findOrFail($id);
    }

    public function checkCancel()
    {
        // 有可能数据变化，因此重新获取数据
        $this->spider = Spider::findOrFail($this->spider->id);
        if ($this->spider->status == 'cancelling') {
            $this->spider->status = 'cancelled';
            $this->spider->reason = 'manual';
            $this->spider->finished_at = DB::raw('now()');
            $this->spider->save();
            return true;
        }
        return false;
    }

    public function terminateProc(){
        try {
            $handle = $this->proc;
            if (!$handle) {
                return;
            }
            $status = proc_get_status($handle);
            $ppid = $status['pid'];
            if (is_resource($this->pipes[1])) {
                fclose($this->pipes[1]);
            }
            if (strncasecmp(PHP_OS, 'WIN', 3) == 0) {
                exec('taskkill /F /T /PID ' . $ppid);
            } else {
                if($status['running'] == true) {
                    $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid $ppid`);
                    foreach($pids as $pid) {
                        if(is_numeric($pid)) {
                            echo "Killing $pid\n";
                            posix_kill($pid, 9);
                        }
                    }
                    proc_close($handle);
                }
            }
        } catch (\Exception $e) {

        }
    }

    public function addStdOut($content){
        $result = $this->spider->stdout. trim($content) . PHP_EOL;
        if(strlen($result) <= 60000){
            $this->spider->stdout = $result;
            $this->spider->save();
        }
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $id = $this->spider->id;
        $key=$this->spider->key;

        // sanitize
        $key = str_replace("'","",$key);
        $key = str_replace("&","",$key);
        $key = str_replace('"',"",$key);
        $key = str_replace('#',"",$key);
        $key = str_replace('.',"",$key);
        $key = str_replace('\\',"",$key);
        $key = str_replace('/',"",$key);
        $key = str_replace('$',"",$key);
        $key = str_replace('`',"",$key);
        $key = str_replace(' ',"+",$key);
        $key = str_replace('-',"",$key);
        $key = str_replace('=',"",$key);

        $type = $this->spider->type;
        $maxCount=$this->spider->max_count;
        $command = '';


        $seperator = strtoupper(substr(PHP_OS,0,3))==='WIN'?'&':';';
        $pwd = strtoupper(substr(PHP_OS,0,3))==='WIN'?'chdir':'pwd';
        $prefix = strtoupper(substr(PHP_OS,0,3))==='WIN'?'':'';

        if ($type=='jd'){
            $command="cd {$prefix}books_jd $seperator $pwd $seperator whoami $seperator scrapy crawl books -a jobID=$id -a key=$key -a maxCount=$maxCount 2>&1";
        }else if($type=='dd'){
            $command="cd {$prefix}books_dd $seperator $pwd $seperator whoami $seperator scrapy crawl books -a jobID=$id -a key=$key -a maxCount=$maxCount 2>&1";
        }else if($type=='isbn'){
            $command="echo 'isbn_task jobID=$id key=$key' 2>&1";
        }else if($type=='comment'){
            $ids=[];

            foreach(DDBook::where('isbn', $key)->selectRaw('distinct dd_id')->get() as $book){
                array_push($ids, $book->dd_id);
            }
            $ids_string=join(',',$ids);
            $command="cd {$prefix}comments_jd $seperator $pwd $seperator whoami $seperator scrapy crawl comments -a jdIds=$ids_string -a isbn=$key 2>&1";
        }else{
            $this->spider->status = 'failed';
            $this->spider->reason = 'job type invalid';
            $this->spider->finished_at = DB::raw('now()');
            $this->spider->save();
        }

        // 执行之前检查是否已被用户取消
        if ($this->checkCancel()) {
            return;
        }
        // 之前的垃圾任务清理
        Spider::where('status', 'running')->update([
            'status' => 'failed',
            'reason' => 'worker terminated',
            'finished_at' => DB::raw('now()')
        ]);
        Spider::where('status', 'queuing')->where('id', '<', $id)->update([
            'status' => 'failed',
            'reason' => 'worker terminated',
            'finished_at' => DB::raw('now()')
        ]);
        Spider::where('status', 'cancelling')->where('id', '<', $id)->update([
            'status' => 'failed',
            'reason' => 'worker terminated',
            'finished_at' => DB::raw('now()')
        ]);
        // 防止 PHP 自动退出
        set_time_limit(0);

        // 开始执行
        try {
            $this->spider->status = 'running';
            $this->spider->started_at = DB::raw('now()');
            $this->addStdOut("cmd: $command");

            // 监控运行状态
            $status='failed';
            $reason='spider crashed';

            // 开始执行
            $descriptorspec = array(
                0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
                1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
                2 => array("pipe", "w") // stderr is a file to write to
            );
            $handle = proc_open(
                $command,
                $descriptorspec,
                $pipes,
                null, null
            );
            $this->proc = $handle;
            $this->pipes=$pipes;
            fclose($pipes[0]);
            fclose($pipes[2]);
            while (True) {
                // 每次输出检查是否已经被取消
                if ($this->checkCancel()) {
                    // 清理并退出
                    $this->terminateProc();
                    return;
                }

                $output = fgets($pipes[1]);
                if ($output == False) {
                    fclose($pipes[1]);
                    break;
                }

                $this->addStdOut(mb_convert_encoding($output, "UTF-8"));

                if (str_contains($output, 'customJson{"type": "success", "value": "complete"}')) {
                    $status='finished';
                    $reason='complete';
                }
                if (str_contains($output, 'customJson{"type": "success", "value": "max page"}')) {
                    $status='finished';
                    $reason='max page';
                }
                if (str_contains($output, 'customJson{"type": "fail", "value": "proxy error"}')) {
                    $status='failed';
                    $reason='proxy error';
                }
                if (str_contains($output, 'customJson{"type": "fail", "value": "parse error"}')) {
                    $status='failed';
                    $reason='parse error';
                }
            }
            $this->terminateProc();
            $this->spider->status = $status;
            $this->spider->reason = $reason;
            $this->spider->finished_at = DB::raw('now()');
            $this->spider->save();
        } catch (\Exception $e) {
            // 发生错误，保存错误信息
            try{
                $this->addStdOut(mb_convert_encoding($e->getMessage(), "UTF-8"));
            }
            catch (\Exception $e){
                $this->fail();
            }
        }

    }

    public function failed()
    {
        // 清理现场
        $this->terminateProc();
//        $count = JDBook::where('job', $this->spider->id)->count();
//        JDBook::where('job', $this->spider->id)->delete();
        $this->spider->status = 'failed';
        $this->spider->reason = 'job exception';
        $this->spider->finished_at = DB::raw('now()');
//        $this->spider->stdout .= "Cleaned saved $count records" . PHP_EOL;
        $this->spider->save();
    }

    public $tries = 1;
    public $timeout = 0; // no timeout
}
